package org.intellij.doctree;

import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.peer.PeerFactory;
import com.intellij.ui.GuiUtils;
import com.intellij.ui.TitlePanel;
import com.intellij.ui.UIHelper;
import com.intellij.util.ui.Tree;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.intellij.doctree.node.*;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ProjectDocs implements ProjectComponent, JDOMExternalizable, DocTreeComponent {

    public static final String ACTION_PLACE = "ProjectDocTreeActionPlace";
    public static final String COMPONENT_NAME = "org.intellij.doctree.ProjectDocs";
    public static final String TOOL_WINDOW_ID = "DocTree";
    public static final Icon TOOL_WINDOW_ICON = IconLoader.getIcon("/org/intellij/doctree/resources/docNode.png", ProjectDocs.class);

    private final List<TitlePanel> titlePanelList = new ArrayList<TitlePanel>();

    private DocTree applicationTree;

    private DocTreeModel projectTreeModel = new DocTreeModel(FolderNode.PROJECT_ROOT);

    private Project project;
    private DocTree projectTree;


    public ProjectDocs(Project project) {
        this.project = project;
    }

    public void initComponent() {
        //Do nothing
    }

    public void disposeComponent() {
        //Do nothing
    }

    @NotNull
    public String getComponentName() {
        return COMPONENT_NAME;
    }

    public void projectOpened() {
        initToolWindow();

        try {
            ProjectDocsWorkspace workspaceComp = (ProjectDocsWorkspace) project.getComponent(ProjectDocsWorkspace.COMPONENT_NAME);
            List<String> projExpList = workspaceComp.getProjExpRowList();
            if (projExpList != null) {
                TreeUtil.restoreExpandedPaths(projectTree, getPathsForRows(projectTreeModel, projExpList));
            }

            List<String> appExpList = workspaceComp.getAppExpRowList();
            if (appExpList != null) {
                ApplicationDocs appComponent = (ApplicationDocs) ApplicationManager.getApplication().getComponent(ApplicationDocs.COMPONENT_NAME);
                TreeUtil.restoreExpandedPaths(applicationTree, getPathsForRows(appComponent.getTreeModel(), appExpList));
            }
        } catch (Exception e) {
            //ignore it
        }
    }                 

    public void projectClosed() {
        unregisterToolWindow();
        project = null;
    }

    public List<String> getAppTreeExpandedState() {
        if (applicationTree == null) return null;
        List<TreePath> treePaths = TreeUtil.collectExpandedPaths(applicationTree);
        return getRowsForPaths(treePaths);
    }

    public List<String> getProjTreeExpandedState() {
        if (projectTree == null) return null;
        List<TreePath> treePaths = TreeUtil.collectExpandedPaths(projectTree);
        return getRowsForPaths(treePaths);
    }

    private List<String> getRowsForPaths(List<TreePath> treePaths) {
        List<String> rowList = new ArrayList<String>();
        for (TreePath treePath : treePaths) {
            String strPath = "";
            for (int j = 1; j < treePath.getPathCount(); j++) {
                DocTreeNode pathComp = (DocTreeNode) treePath.getPathComponent(j);
                DocTreeNode parentComp = (DocTreeNode) treePath.getPathComponent(j - 1);
                if (j > 1) strPath += ",";
                strPath += parentComp.getIndex(pathComp);
            }
            rowList.add(strPath);
        }
        Collections.sort(rowList);
        return rowList;
    }

    private List<TreePath> getPathsForRows(TreeModel treeModel, List<String> rowList) {
        List<TreePath> treePathList = new ArrayList<TreePath>();
        for (String row : rowList) {
            String[] indexes = row.split(",");
            DocTreeNode[] path = new DocTreeNode[indexes.length + 1];
            path[0] = (DocTreeNode) treeModel.getRoot();
            for (int j = 0; j < indexes.length; j++) {
                int idx = Integer.valueOf(indexes[j]);
                path[j + 1] = (DocTreeNode) path[j].getChildAt(idx);
            }
            TreePath treePath = new TreePath(path);
            treePathList.add(treePath);
        }
        return treePathList;
    }

    private void initToolWindow() {
        ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);

        JPanel contentPanel = new JPanel(new BorderLayout());

        projectTree = new DocTree(this);
        JPanel projPanel = buildDocPanel("Project Documents", TOOLBAR_ACTION_GROUP, POPUP_ACTION_GROUP, ACTION_PLACE, this, projectTree);

        ApplicationDocs appComponent = (ApplicationDocs) ApplicationManager.getApplication().getComponent(ApplicationDocs.COMPONENT_NAME);
        applicationTree = new DocTree(appComponent);
        JPanel appPanel = buildDocPanel("Application Documents", TOOLBAR_ACTION_GROUP, POPUP_ACTION_GROUP, ApplicationDocs.ACTION_PLACE, appComponent, applicationTree);

        JSplitPane splitPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, appPanel, projPanel);
        splitPanel.setDividerLocation(0.5);
        splitPanel.setContinuousLayout(true);
        splitPanel.setOneTouchExpandable(true);
        contentPanel.add(splitPanel, BorderLayout.CENTER);
        GuiUtils.replaceJSplitPaneWithIDEASplitter(contentPanel);

        ToolWindow toolWindow = toolWindowManager.registerToolWindow(TOOL_WINDOW_ID, contentPanel, ToolWindowAnchor.LEFT);
        toolWindow.setTitle("");
        toolWindow.setIcon(TOOL_WINDOW_ICON);
    }

    private JPanel buildDocPanel(String title, final String toolbarGroupName, final String popupGroupName,
                                 final String actionPlace, final DocTreeComponent docTreeComponent, final Tree tree) {
        ActionGroup actionGroup = (ActionGroup) ActionManager.getInstance().getAction(toolbarGroupName);
        ActionToolbar toolBar = ActionManager.getInstance().createActionToolbar(actionPlace, actionGroup, true);
        JPanel docPanel = new JPanel(new BorderLayout());
        docPanel.setBackground(UIUtil.getTreeTextBackground());

        tree.setModel(docTreeComponent.getTreeModel());
        TreeUtil.installActions(tree);
        UIHelper uiHelper = PeerFactory.getInstance().getUIHelper();
        //method signature changed from "installTreeSpeedSearch(Tree)" to "installTreeSpeedSearch(JTree)"
        try {
            try {
                Method installMethod = UIHelper.class.getMethod("installTreeSpeedSearch", JTree.class);
                installMethod.invoke(uiHelper, tree);
            } catch (Exception e) {
                Method installMethod = UIHelper.class.getMethod("installTreeSpeedSearch", Tree.class);
                installMethod.invoke(uiHelper, tree);
            }
        } catch (Throwable t) {
            System.err.println("Unable to install tree speed search");
            t.printStackTrace(System.err);
        }
        ToolTipManager.sharedInstance().registerComponent(tree);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setTransferHandler(new DocTreeTransferHandler());
        tree.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                Point point = e.getPoint();
                TreePath path = tree.getClosestPathForLocation(point.x, point.y);
                if (e.getClickCount() == 2) {
                    if (path == null) return;
                    openDocument(path);
                } else if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON3) {
                    ActionGroup actionGroup = (ActionGroup) ActionManager.getInstance().getAction(popupGroupName);
                    ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(actionPlace, actionGroup);
                    popupMenu.getComponent().show(tree, point.x, point.y);
                }
            }

        });
        JScrollPane scrollPane = new JScrollPane(tree);

        JPanel topPanel = new JPanel(new BorderLayout());
        final TitlePanel titlePanel = new TitlePanel();
        titlePanel.setText(title);
        topPanel.add(titlePanel, BorderLayout.NORTH);
        JComponent toolBarComp = toolBar.getComponent();
        topPanel.add(toolBarComp, BorderLayout.CENTER);
        docPanel.add(topPanel, BorderLayout.NORTH);
        docPanel.add(scrollPane, BorderLayout.CENTER);

        registerPanel(titlePanel);

        FocusListener focusListener = new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                enablePanel(titlePanel);
            }
        };

        MouseListener mouseListener = new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                enablePanel(titlePanel);
            }
        };

        titlePanel.addMouseListener(mouseListener);
        toolBarComp.addFocusListener(focusListener);
        toolBarComp.addMouseListener(mouseListener);
        for (int i = 0; i < toolBarComp.getComponentCount(); i++) {
            toolBarComp.getComponent(i).addMouseListener(mouseListener);
        }
        tree.addFocusListener(focusListener);

        return docPanel;
    }

    private void registerPanel(TitlePanel panel) {
        titlePanelList.add(panel);
    }

    private void enablePanel(TitlePanel panel) {
        if (panel == null) return;
        for (TitlePanel t : titlePanelList) {
            if (t == null) continue;
            if (panel.equals(t)) {
                t.setActive(true);
            } else {
                t.setActive(false);
            }
        }
    }

    protected void openDocument(TreePath path) {
        TreeNode node = (TreeNode) path.getLastPathComponent();
        if (node instanceof DocumentNode) {
            Document doc = ((DocumentNode) node).getDocument();
            String url = doc.getURL();
            if (url == null) return;
            url = Document.expandMacros(url);
            BrowserUtil.launchBrowser(url);
        }
    }

    private void unregisterToolWindow() {
        ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
        toolWindowManager.unregisterToolWindow(TOOL_WINDOW_ID);
    }

    public void readExternal(Element element) throws InvalidDataException {
        XMLHelper.readDocuments(element, projectTreeModel);
    }

    public void writeExternal(Element element) throws WriteExternalException {
        XMLHelper.writeDocuments(element, projectTreeModel);
    }

    public void addFolder(FolderNode parent, Folder newFolder) {
        projectTreeModel.insertNodeInto(new FolderNode(newFolder), parent, parent.getChildCount());
    }

    public void addDocument(FolderNode parent, Document newDocument) {
        projectTreeModel.insertNodeInto(new DocumentNode(newDocument), parent, parent.getChildCount());
    }

    public void updateNode(FolderNode originalNode, Folder folder) {
        originalNode.setFolder(folder);
        projectTreeModel.nodeChanged(originalNode);
    }

    public void updateNode(DocumentNode originalNode, Document document) {
        originalNode.setDocument(document);
        projectTreeModel.nodeChanged(originalNode);
    }

    public void removeNode(DocTreeNode node) {
        projectTreeModel.removeNodeFromParent(node);
    }

    public DocTree getApplicationTree() {
        return applicationTree;
    }

    public DocTree getProjectTree() {
        return projectTree;
    }

    public TreeModel getTreeModel() {
        return projectTreeModel;
    }

    public void moveNodeUp(DocTreeNode node) {
        projectTreeModel.moveNodeUp(node);
    }

    public void moveNodeDown(DocTreeNode node) {
        projectTreeModel.moveNodeDown(node);
    }

}
